from asyncore import dispatcher
from asynchat import async_chat
import socket, asyncore, time, sys, ConfigParser
from login import *
from admin import *
import template as tpl
from earthdawn.edactions import command_queue, pop_all
from random import choice
#from earthdawn.utils import flatten
#from earthdawn.edactions import EDActions 
from earthdawn.auxactions import saveAllPlayers, saveRandomPlayer, savePlayer, removeLinkDeadActors

import sys


from model import Select, Update
Update = Update()
Select = Select()
aux_queue = [] # used to handle mud maintainance and non-PC-generated actions

from TelnetIAC import TelnetTalk

# Initial configuration file reading.
config = ConfigParser.ConfigParser()
config.read("eris.conf")

# Global dictionaries holding the player's ID, socket and IP address.
sessions = {}
ipsessions = {}


logger = open(config.get("logging", "logfile"), 'a')
#logger = open("main.log", 'a')
def Log(line):
    "Just call Log('text') to log to the main log file."
    timer = time.asctime()
    logger.write("%s %s\n" % (line, timer))
    logger.flush()
    



class Handler:
    "Command parser, where all player commands go."

    def unknown(self, session, cmd):
        session.push(tpl.UNKNOWN_CMD % str(cmd))

    def handle(self, session, line):
        #Time of last command executed. Will be used for external timeout cleanup.
        Update.setLastAction(time.time(), session.p_id)

        if not line.strip(): session.push("> ")      
        
        # Filter the telnet commands
        self.tiac = TelnetTalk()
        self.ndata = self.tiac.listen(line)
        line = self.ndata[0]

        session.push(self.ndata[1]) # Reply accordingly

        parts = line.split(' ', 1)
        cmd = parts[0]
        if not cmd: pass

        # Easier to do it here, as EndSession is defined outside of action.py
        elif (cmd == "logout") or (cmd == "quit"):
            # need to write out to DB and clear the command_queue of all the PCs actions
            savePlayer(session.p_id)
            raise EndSession  ### some error here
        

        else:
            try: line = parts[1].strip()
            except IndexError: line = ''
            meth = getattr(Actions, 'do_'+cmd, None) # Normal commands, actions.py

            if cmd.find('@') == 0 :
                methadm = getattr(Admincmd, 'do_'+cmd.strip('@'), None) # Admin commands, admin.py
                if session.pname in self.coders and callable(methadm):
                    methadm(session, line)
                    session.push("> ")
                else:
                    self.unknown(session, cmd)
                    session.push("> ")


            elif cmd in long_commands:
                # need to figure out what called us
                callerName = sys._getframe(1).f_code.co_name  
                # either this was called by 'found_terminator' or by 'processCommandQueue'
                # if this was called by 'found_terminator' then put on the stack, REPLACING any previous action
                if callerName == 'found_terminator':
                    #print "start found terminator at %s" % time.asctime() 
                    needToAddFlag = True # check to see if there are any actions to replace
                    for each in command_queue:
                        if each[1] == session.p_id:
                            timestore = each[0] # get time so no ticks are wasted
                            prevcommand = each [2]
                            newcommand = cmd + ' ' + line

                            # inform player if action was unncecessary
                            if newcommand == prevcommand:
                                session.pushline('But you were already performing that action?!')
                            else:
                                command_queue.remove(each) # remove item to prevent spamming exploits
                                command_queue.append((each[0]-.5, session.p_id, cmd + ' ' + line)) # stick new item on stack
                                processCommandQueue()
                            needToAddFlag = False
                            
                    if (needToAddFlag): #if nothing was replaced, then just add
                        session.pushline('You begin your action.')
                        command_queue.append((time.time(), session.p_id, cmd + ' ' + line)) # stick new item on stack
                    #print "before found terminator at %s" % time.asctime() 
                    #processCommandQueue()
                    #print "after found terminator at %s" % time.asctime() 
                            
                
                # if this was called by 'processCommandQueue'... then
                #fire off at least 1 command
                #meth(session, line)
                #and execute all on left on stack ready to go (shouldn't be a lot but you never know)
                #count = 0
                #queueLength = len(command_queue)
                #while count < queueLength:
                    #command_queue.remove(command_queue[count])
                    #meth(session, line)                   
                    #count = count + 1
                ##for each in command_queue:
                    ##if each[1] == session.p_id and each[0] < time.time():
                        ##command_queue.remove(each)
                        ##meth(session, line)
                ##add default action
                #command_queue.append((time.time()+5, session.p_id, cmd + ' ' + line)) # stick new item on stack
                
                #############################
                elif callerName == 'processCommandQueue':   
                    #print "start process command call at %s" % time.asctime() 
                    #fire off at least 1 command
                    #meth(session, line)
                    #and execute all on left on stack ready to go (shouldn't be a lot but you never know)
                    count = 0
                    queueLength = len(command_queue)
                    while count < queueLength:
                        command_queue.remove(command_queue[count])
                        meth(session, line)                   
                        count = count + 1
                    #for each in command_queue:
                        #if each[1] == session.p_id and each[0] < time.time():
                            #command_queue.remove(each)
                            #meth(session, line)
                    #add default action
                    #print "before process command enqueue at %s" % time.asctime()
                    command_queue.append((time.time()+5, session.p_id, cmd + ' ' + line)) # stick new item on stack
                    #print "end process command call at %s" % time.asctime()
                #############################
                
                ##put on stack to fire on next tick after popping all other actions in case of spam
                #for each in command_queue:
                    #if each[1] == session.p_id:
                        #if each[0] < time.time():
                            #temp = time.time()
                            ##execute the function
                            #timestore = each[0]
                            #command_queue.remove(each)
                            #command_queue.append((each[0], session.p_id, cmd + ' ' + line))
                            ##meth(session, line)
                            #print 'got in' + str(time.time())
                            ##session.push("> ")
                        #else:
                            #meth(session, line)
                            ##command_queue.remove(each)
                #if vars().has_key('each'): # queue was not empty
                    #meth(session, line)
                    #command_queue.append((time.time() + 5, session.p_id, cmd + ' ' + line))
                    #print 'got out' + str(time.time())
                    #session.push("> ")
                    
                #else: #queue was empty -- run the command
                    #command_queue.append((time.time() + 5, session.p_id, cmd + ' ' + line))
                ##command_queue.append((time.time() + 5, session.p_id, cmd + ' ' + line))
                
            elif callable(meth): # Normal command
                meth(session, line)
                session.push("> ")
            else:
                self.unknown(session, cmd)
                session.push("> ")


class EndSession(Exception): pass


class EnterGame(Handler):

    def __init__(self, session):
        self.session = session
        self.admin = []
        self.coders = []

        #Coders file, for in-game creators/wizards/builders
        self.coder = open('coders', 'r')
        self.icoder = iter(self.coder)
        for line in self.icoder:
            self.coders.append(line.strip('\n'))
        self.coder.close()

    def enter(self, room):
        self.room = room
        room.add(self, self.session)

    def found_terminator(self):
        line = (''.join(self.data))
        self.data = []
        if not line: session.push("> ")
        else:
            try: self.handle(self, line)
            except: raise EndSession

    def add(self, session):
        self.ival = ipsessions.items()

        if session.addr[0] != "127.0.0.1" and session.addr[0] in self.ival: # Allow multiusers for the admin.
            session.push(tpl.LOGIN_ALREADY_IN)
            raise EndSession

        sessions[session.p_id] = session
        ipsessions[session.p_id] = session.addr[0]
        Update.setIP(session.addr[0], session.p_id)

        #Store the player's starting location in the socket object.
        session.is_in = 1
        Update.setLocation(session.p_id, 1)

        # Check if the user supports ANSI colors
        if Select.getColors(session.p_id) == "on": Actions.do_setansi(session, "on")
        else: Actions.do_setansi(session, "off")

        for i in sessions:
            try:
                i.push(tpl.ENTER_GAME % (session.pname,))
            except: pass

        print "%s logged in." % session.pname
        Log("%s logged in from %s" % (session.pname,session.addr[0]))
        Actions.do_look(session, '') # To be replaced with something nicer.
        session.push("> ")

        #####
        #import browser1
        #browser1.dumpObj(session)

class SecondServSock(async_chat):
    #The chat server, instanced for all users

    def __init__(self, server, sock, addr):
        async_chat.__init__(self, sock)
        self.server = server
        self.ipaddr = addr
        self.set_terminator("\n")
        self.name = None
        self.data = []
        self.sock = sock
        self.enter(FirstLogin(server, addr[0])) #Call the login procedure before anything.

    def collect_incoming_data(self, data):
        self.data.append(data)
    def found_terminator(self):
        line = (''.join(self.data))
        line = line.strip('\r')
        self.tiac = TelnetTalk()
        self.ndata = self.tiac.listen(line)
        line = self.ndata[0]
        self.data = []

        try: 
            self.room.handle(self, line)
        except EndSession:
            self.handle_close()

    def pushline(self, data):
        self.producer_fifo.push (simple_producer (data + '\n'))
        self.initiate_send()

    def handle_close(self):
        self.ival = ipsessions.items()
        for i in self.ival:
            self.test = str(sessions[i[0]])
            self.tesb = str(self.sock)
            try:
                if (self.ipaddr[0] == i[1]) & (self.test == self.tesb):
                    self.leaver = Select.getPlayerByID(i[0])
                    self.leaver = self.leaver[1].capitalize()
                    #In any case, if the player exits, remove him.
                    Update.LogoutPlayer(i[0])
                    Log("%s disconnected from %s" % (self.leaver, self.ipaddr[0]))
                    print "%s logged out." % self.leaver

                    del sessions[i[0]]
                    del ipsessions[i[0]]

                    self.locali = Select.getAllPlayersWithIP()

                    for j in self.locali:
                        self.tmpsec = sessions[j[0]]
                        if self.tmpsec == None: # Shouldn't happen, do some cleaning
                            Update.LogoutPlayer(j[0])
                        else:
                            self.tmpsec.push(tpl.LEAVE_GAME % (self.leaver,))

                    async_chat.handle_close(self)
                else: raise
            except: raise

    def enter(self, room):
        self.room = room
        room.add(self)


###
# Time related actions
# * All commented out for now since it's not used.
###

#schedule = {'heal':100.0}
#lastrun = {}

#def heal():
    #pass
    ###cu.execute("update pnames set curhp=curhp+1 where p_id=? and curhp<maxhp", session.p_id)


### The timer loop
#def Timer(timer):

    #sched = schedule.iteritems()
    #for i in sched:
        #try: lastrun[i[0]]
        #except: lastrun[i[0]] = time.time()

        #diff = timer - lastrun[i[0]]

        ### Every 100 seconds, run heal(), defined above.
        #if diff >= schedule['heal']:
            #heal()
            #lastrun['heal'] = time.time()


class MainServSock(dispatcher):
    # The Server

    def __init__(self, port):
        dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind(('', port))
        self.listen(int(config.get("server", "max_users")))
        self.enterg = EnterGame(self)
    def handle_accept(self):
        conn, addr = self.accept()
        SecondServSock(self, conn, addr)


def processCommandQueue():
    #while len(command_queue) > 0:  # will stick in while loop...shouldn't get here without a command anyway
    command_queue.sort(lambda x,y:cmp(x[0],y[0])) #sort according to timestamp
    start, actor, message = command_queue[0]    
    if start <= time.time():
        try:
            sessions[actor].room.handle(sessions[actor], message) # this looks kinda ugly
        except:
            print "Flushing player's CommandQueue"
            try:
                command_queue.pop()
            except:
                print "Error while trying to pop from CommandQueue"

def processAuxQueue():
    aux_queue.sort(lambda x,y:cmp(x[0],y[0])) #sort according to timestamp
    start, command = aux_queue[0]    
    if start <= time.time():
        try:
            
            eval(command, globals())
        except:
            print "Problem with aux_queue command:  %s" % command
            try:
                aux_queue.pop()
            except:
                print "Error while trying to pop from aux_queue"


def mytest():
    print "time is %s" % time.asctime()
                
if __name__ == '__main__':
    s = MainServSock(int(config.get("server", "port")))
    Update.resetAllIPs()

    try:
        import actions
        Actions = actions.Actions(sessions)
        Admincmd = AdminCmds(sessions, ipsessions)
        long_commands = ['kill', 'cast']
        print "Accepting connections..."
        # set up aux (non-player-generated) commands
        aux_queue.append((time.time() + 300 , 'removeLinkDeadActors(Select, Update, aux_queue)')) # remove all link dead players from world 5 minutes
        aux_queue.append((time.time() + 150 , 'saveRandomPlayer(sessions, Select, Update, aux_queue)')) # save a random (logged in) player every 5 minutes 
                                                                                   # with a start time in 2.5 mins
        
        while 1:
            
            #asyncore.loop(timeout=5, count=1) # Timer() called every 5 seconds.
            asyncore.loop(count=1)
            if (len(aux_queue) > 0):
                processAuxQueue()

            #print "Main fired at %s" % time.asctime()
            if (len(command_queue) > 0):
                processCommandQueue()


    except KeyboardInterrupt: #print
        print

